package com.progenies.ecg.asm31.model.factory;

import java.lang.reflect.Field;
import java.util.Collections;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Type;

import com.progenies.ecg.asm31.model.codeparts.IConfigurableClassObject;
import com.progenies.ecg.asm31.model.codeparts.IConfigurableConstructorObject;
import com.progenies.ecg.asm31.model.codeparts.IConfigurableMethodObject;
import com.progenies.ecg.asm31.util.GenerationUtils;
import com.progenies.ecg.asm31.util.VariableRegister;
import com.progenies.ecg.generator.ClassGeneratorFactory;
import com.progenies.ecg.model.ClassObject;
import com.progenies.ecg.model.ConstructorObject;
import com.progenies.ecg.model.FieldObject;
import com.progenies.ecg.model.MethodObject;

final class ClassObjectASM31 extends ClassObject implements IConfigurableClassObject
{
	
	private ClassVisitor visitor;
	private int javaVersion;
	
	private VariableRegister varRegister;
	
	ClassObjectASM31(String clsName, Class<?> superClass)
	{
		super();
		this.name=clsName;
		this.superClass=superClass;
		this.varRegister=new VariableRegister();
	}
	
	@Override
	public void generateBytecode()
	{
		if(this.visitor==null || javaVersion==0)
			throw new IllegalStateException("Class has not been configurated, invoke 'configure' first or use ClassGenerator");
		
		//Step 1: Visit the class
		visitor.visit(javaVersion, GenerationUtils.getAccessTranslated(this.modifiers), GenerationUtils.translateClassName(name), null, Type.getType(superClass).getInternalName(), getInterfacesTranslated());
		
		//Register the first local variable index: always 'this' reference, with index 0
		varRegister.registerLocalVariable("this", Type.getType(Object.class));
		
		//First, if no constructors are configured, I'll create a default one (needed by field initializers
		if(this.constructors.size()==0)
			this.constructors.add(ClassGeneratorFactory.getClassGenerator().objectFactory.constructors.createConstructor());
		
		//Step 2: Create fields
		if(this.fields.size()>0)
		{
			//TODO: Maybe i should check field dependences to set the correct order, now i'm just using the user order
			//reverse order: the initialization code is inserted at position 1, so i want the first of the list at position 1.
			Collections.reverse(fields); 
			for(FieldObject<?> fieldObj : this.fields)
			{
				//register field
				varRegister.registerField(fieldObj.getName(), Type.getType(fieldObj.getClassType()), fieldObj.getModifiers());
				
				((FieldObjectASM31<?>)fieldObj).configure(this.visitor, this);
				
				fieldObj.generateBytecode();
			}			
		}
		
		//Using reflection, i'll register fields from the superClass
		Class<?> tmpClass=this.superClass;
		while(tmpClass!=null)
		{
			for(Field fld : tmpClass.getDeclaredFields())
			{
				varRegister.registerField(fld.getName(), Type.getType(fld.getType()), GenerationUtils.translateModifiers(fld.getModifiers()));
			}
			tmpClass=tmpClass.getSuperclass();
		}
		
		//Step 3: Create constructors
		
		
		for(ConstructorObject constrObj : this.constructors)
		{
			((IConfigurableConstructorObject)constrObj).configure(this.visitor, this, new VariableRegister(varRegister));
			constrObj.generateBytecode();
		}
		
		//Step 4: Create method
		for(MethodObject methObj : this.methods)
		{
			((IConfigurableMethodObject)methObj).configure(this.visitor, this, new VariableRegister(varRegister));
			methObj.generateBytecode();
		}
		
		//TODO: Support of internal classes
		
		//Step 5: End!
		visitor.visitEnd();
	}

	/* (non-Javadoc)
	 * @see com.progenies.ecg.asm31.model.factory.IConfigurableClassObject#configure(int, org.objectweb.asm.ClassVisitor)
	 */
	@Override
	public void configure(int javaVersion, ClassVisitor visitor)
	{
		this.javaVersion=javaVersion;
		this.visitor=visitor;
	}
	

	
	private String[] getInterfacesTranslated()
	{
		String iFaces[]=null;
		if(this.interfaces!=null && this.interfaces.length>0)
		{
			iFaces=new String[this.interfaces.length];
			for(int i=0;i<iFaces.length;i++)
				iFaces[i]=Type.getType(this.interfaces[i]).getInternalName();
		}
		return iFaces;
	}

}
